package niu.toy.spring.mvc;

import cn.hutool.core.util.SystemPropsUtil;
import niu.toy.spring.ioc.annotation.Nullable;
import niu.toy.spring.ioc.impl.DefaultListableBeanFactory;
import niu.toy.spring.support.BeansException;
import org.apache.catalina.Host;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.startup.Tomcat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.ServletContainerInitializer;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;
import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 创建ApplicationContext(简单的，没有复杂的东西)及webServer，
 */
public class WebApplicationContext  implements ServletContainerInitializer {

    private static final Logger logger = LoggerFactory.getLogger(WebApplicationContext.class);

    @Nullable
    private ServletContext servletContext;
    private final DefaultListableBeanFactory beanFactory;

    private File baseDirectory;
    public static final String DEFAULT_PROTOCOL = "org.apache.coyote.http11.Http11NioProtocol";
    public static final String APPLICATION_CONTEXT_ROOT="spring_context_root";
    private String contextPath = "";
    private static final Set<Class<?>> NO_CLASSES = Collections.emptySet();
    int port=8080;
    private static final AtomicInteger containerCounter = new AtomicInteger(-1);
    private Tomcat webServer;

    private DispatcherServlet servlet;

    public WebApplicationContext(String basePackage) {
        this.beanFactory = new DefaultListableBeanFactory(basePackage);
    }


    public DefaultListableBeanFactory getBeanFactory() {
        return beanFactory;
    }

    public  <T> T getBean(Class<T> cls){
        return this.beanFactory.getBean(cls);
    }

    public void setBean(Class<?> cls, Object obj){
        this.beanFactory.setBean(cls,obj);
    }



    @Override
    public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException {
        logger.info("注册dispatcherServlet");
        ServletRegistration.Dynamic registration=servletContext.addServlet("dispatcherServlet", this.servlet);
        registration.addMapping("/*");
        registration.setLoadOnStartup(-1);
        prepareWebApplicationContext(servletContext);
    }
    protected void prepareWebApplicationContext(ServletContext servletContext) {
        Object rootContext = servletContext.getAttribute(APPLICATION_CONTEXT_ROOT);
        if (rootContext != null) {
            if (rootContext == this) {
                throw new IllegalStateException(
                        "Cannot initialize context because there is already a root application context present - "
                                + "check whether you have multiple ServletContextInitializers!");
            }
            return;
        }
        servletContext.log("Initializing Spring embedded WebApplicationContext");
        try {
            servletContext.setAttribute(APPLICATION_CONTEXT_ROOT, this);
            if (logger.isDebugEnabled()) {
                logger.debug("Published root WebApplicationContext as ServletContext attribute with name ["
                        + APPLICATION_CONTEXT_ROOT + "]");
            }
//            setServletContext(servletContext);
        }
        catch (RuntimeException | Error ex) {
            logger.error("Context initialization failed", ex);
            servletContext.setAttribute(APPLICATION_CONTEXT_ROOT,ex);
            throw ex;
        }
    }
    public Tomcat createWebServer() {
        Tomcat tomcat = new Tomcat();
        File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir(DEFAULT_PROTOCOL);
        tomcat.setBaseDir(baseDir.getAbsolutePath());

        Connector connector = new Connector(DEFAULT_PROTOCOL);
        connector.setThrowOnFailure(true);
        connector.setPort(Integer.parseInt(SystemPropsUtil.get("server.port")));

        tomcat.getService().addConnector(connector);
        tomcat.setConnector(connector);
        tomcat.getHost().setAutoDeploy(false);
        configureContext(tomcat.getHost());
        try {
            tomcat.start();
            startDaemonAwaitThread(tomcat);
        } catch (LifecycleException e) {
            e.printStackTrace();
        }
        this.webServer=tomcat;
        return tomcat;
    }

    private void startDaemonAwaitThread(Tomcat tomcat) {
        Thread awaitThread = new Thread("container-" + (containerCounter.get())) {

            @Override
            public void run() {
                tomcat.getServer().await();
            }

        };
        awaitThread.setContextClassLoader(getClass().getClassLoader());
        awaitThread.setDaemon(false);
        awaitThread.start();
    }

    protected StandardContext configureContext(Host host) {
        StandardContext context = new StandardContext();
        String contextPath=SystemPropsUtil.get("server.servlet.contextPath").trim();
        context.setName(contextPath);
        context.setPath(contextPath);
        context.addLifecycleListener(new Tomcat.FixContextListener());

        try {
            context.setCreateUploadTargets(true);
        }
        catch (NoSuchMethodError ex) {
            ex.printStackTrace();
        }
        host.addChild(context);
        context.addServletContainerInitializer(this, NO_CLASSES);
        this.servlet=new DispatcherServlet();
        logger.info("当前Web contextPath:"+contextPath);
        return context;
    }
    protected final File createTempDir(String prefix) {
        try {
            File tempDir = File.createTempFile(prefix + ".", "." + port);
            tempDir.delete();
            tempDir.mkdir();
            tempDir.deleteOnExit();
            return tempDir;
        }
        catch (IOException ex) {
            throw new BeansException(
                    "Unable to create tempDir. java.io.tmpdir is set to " + System.getProperty("java.io.tmpdir"), ex);
        }
    }
}
